/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package es.unavarra.mti.iws.logic.editor;


import es.unavarra.mti.iws.Manager;
import es.unavarra.mti.iws.gui.RenderPanel;
import es.unavarra.mti.iws.logic.undoredo.ModifySelectable;
import es.unavarra.mti.iws.model.Event;
import es.unavarra.mti.iws.model.Link;
import es.unavarra.mti.iws.model.Node;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Asier
 */
public class DefaultEditor {
    public final float CONTROL_POINT_RADIOUS = 10;
    public static final byte STATUS_IDLE = 0;
    public static final byte STATUS_SELECTING = 1;
    public static final byte STATUS_TRANSFORMING = 2;

    public static final byte TRANSFORM_MOVE = 1;
    public static final byte TRANSFORM_SCALE_CENTERED = 2;
    public static final byte TRANSFORM_SCALE = 3;
    public static final byte TRANSFORM_ROTATE = 4;
    public static final byte TRANSFORM_SHEAR = 5;

    public static final int USE_POSITION = 0x00000001;
    public static final int USE_ROT = 0x00000004;
    public static final int USE_SCALEX = 0x00000008;
    public static final int USE_SCALEY = 0x00000010;

    private byte currentStatus;
    private byte currentTransform;
    private boolean forceMoveTransform;
    private boolean useSimpleSelection;
    private boolean isControlPressed;
    
    private byte portTransforming = -1;
    private final ArrayList<Selectable> selection;
    private final HashMap<Selectable,Selectable> selectionHash;
    private Selectable selected;
    private float selectionX, selectionY;
    private float selectionXEnd, selectionYEnd;
    private float selCenterX, selCenterY;
    private int usePattern;
    private float[] oldX,oldY,oldRotation,oldScaleX,oldScaleY;

    private Manager manager;
    private RenderPanel panel;


    public DefaultEditor( Manager manager) {
        this.manager = manager;
        panel = manager.getPanel();
        currentStatus = STATUS_IDLE;
        currentTransform = TRANSFORM_MOVE;
        portTransforming = -1;
        selection = new ArrayList<Selectable>();
        selectionHash = new HashMap<Selectable, Selectable>();
    }


    // <editor-fold defaultstate="collapsed" desc="Apply transforms">

    private void clearOldValues() {
        usePattern = 0;
        oldX = null;
        oldY = null;
        oldRotation = null;
        oldScaleX = null;
        oldScaleY = null;
    }

    private void applySelectionTransform() {
        synchronized (selection) {
            if (!selection.isEmpty()) {
                ModifySelectable op = new ModifySelectable(selection, usePattern,
                        oldX, oldY, oldRotation, oldScaleX, oldScaleY);
                manager.getUndo().addOp(op);
                clearOldValues();
            }
        }
    }

    private void saveSelectionPreviousState(int pattern) {
        synchronized (selection) {
            this.usePattern = pattern;
            int size = selection.size();
            if (size == 0) {
                return;
            }
            if ((usePattern & USE_POSITION) != 0) {
                oldX = new float[size];
                oldY = new float[size];
            }

            if ((usePattern & USE_ROT) != 0) {
                oldRotation = new float[size];
            }
            if ((usePattern & USE_SCALEX) != 0) {
                oldScaleX = new float[size];
            }
            if ((usePattern & USE_SCALEY) != 0) {
                oldScaleY = new float[size];
            }
            int index = 0;
            for (Selectable s : selection) {
                if ((usePattern & USE_POSITION) != 0) {
                    oldX[index] = s.getX();
                    oldY[index] = s.getY();
                }
                if ((usePattern & USE_ROT) != 0 && (s instanceof Rotable)) {
                    oldRotation[index] = ((Rotable) s).getRotation();
                }
                if ((usePattern & USE_SCALEX) != 0 && (s instanceof Scalable)) {
                    oldScaleX[index] = ((Scalable) s).getScaleX();
                }
                if ((usePattern & USE_SCALEY) != 0 && (s instanceof Scalable)) {
                    oldScaleY[index] = ((Scalable) s).getScaleY();
                }
                index++;
            }
        }
    }

    private float[][] getPoints(Selectable s){
        float w2,h2;
        float[][] points = new float[8][2];

        w2 = s.getSizeX() / 2.0f;
        h2 = s.getSizeY() / 2.0f;
        if (s instanceof Scalable){
            w2 *= ((Scalable)s).getScaleX();
            h2 *= ((Scalable)s).getScaleY();
        }

        points[0][0] = -w2;
        points[0][1] = -h2;
        points[2][0] = w2;
        points[2][1] = -h2;
        points[4][0] = w2;
        points[4][1] = h2;
        points[6][0] = -w2;
        points[6][1] = h2;

        if (s instanceof Rotable){
            float cos = (float) Math.cos(((Rotable)s).getRotation() * Math.PI / 180.0f);
            float sin = (float) Math.sin(((Rotable)s).getRotation() * Math.PI / 180.0f);
            float xTemp,yTemp;

            xTemp = points[0][0];
            yTemp = points[0][1];
            points[0][0] = xTemp * cos + yTemp * sin;
            points[0][1] = -xTemp * sin + yTemp * cos;
            xTemp = points[2][0];
            yTemp = points[2][1];
            points[2][0] = xTemp * cos + yTemp * sin;
            points[2][1] = -xTemp * sin + yTemp * cos;
            xTemp = points[4][0];
            yTemp = points[4][1];
            points[4][0] = xTemp * cos + yTemp * sin;
            points[4][1] = -xTemp * sin + yTemp * cos;
            xTemp = points[6][0];
            yTemp = points[6][1];
            points[6][0] = xTemp * cos + yTemp * sin;
            points[6][1] = -xTemp * sin + yTemp * cos;
        }

        float selX = s.getX();
        float selY = s.getY();
        points[0][0] += selX;
        points[0][1] += selY;
        points[2][0] += selX;
        points[2][1] += selY;
        points[4][0] += selX;
        points[4][1] += selY;
        points[6][0] += selX;
        points[6][1] += selY;
        
        points[1][0] = (points[0][0] + points[2][0]) / 2.0f;
        points[1][1] = (points[0][1] + points[2][1]) / 2.0f;
        points[3][0] = (points[2][0] + points[4][0]) / 2.0f;
        points[3][1] = (points[2][1] + points[4][1]) / 2.0f;
        points[5][0] = (points[4][0] + points[6][0]) / 2.0f;
        points[5][1] = (points[4][1] + points[6][1]) / 2.0f;
        points[7][0] = (points[6][0] + points[0][0]) / 2.0f;
        points[7][1] = (points[6][1] + points[0][1]) / 2.0f;

        return points;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="draw">
    private void drawGizmo(Selectable s) {

    }

    private void drawSelection() {
        panel.rectFill(selectionX, selectionY, selectionXEnd - selectionX,
                selectionYEnd - selectionY,
                0x803030FF);
    }
    
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="collisions">
    private int pointInsidePort(Selectable s, float px, float py) {
        float[][] points = getPoints(s);
        float distance = 0.0f;
        for (int i = 0; i < 8; i++) {
            distance = (float) Math.sqrt((px - points[i][0]) * (px - points[i][0]) +
                    (py - points[i][1]) * (py - points[i][1]));
            if (distance <= CONTROL_POINT_RADIOUS) {
                return i;
            }
        }
        return -1;
    }

    public static boolean pointInsideSelectable(Selectable s, float px, float py) {
        float auxX, auxY;

            auxX = px - s.getX();
            auxY = py - s.getY();

        if (s instanceof Rotable) {
            float rotation = ((Rotable) s).getRotation();
            float cos = (float) Math.cos((-rotation) * Math.PI / 180.0f);
            float sin = (float) Math.sin((-rotation) * Math.PI / 180.0f);
            px = auxX * cos + auxY * sin;
            py = -auxX * sin + auxY * cos;
        } else {
            px = auxX;
            py = auxY;
        }

            float scaleX = 1.0f;
            float scaleY = 1.0f;
            if(s instanceof Scalable){
                scaleX = ((Scalable)s).getScaleX();
                scaleY = ((Scalable)s).getScaleY();
            }

            if (px >= -(s.getSizeX() * Math.abs(scaleX) / 2.0f)
                    && px <= s.getSizeX() * Math.abs(scaleX) / 2.0f
                    && py >= -(s.getSizeY() * Math.abs(scaleY) / 2.0f)
                    && py <= s.getSizeY() * Math.abs(scaleY) / 2.0f) {
                return true;
            }
        
        return false;
    }

    private boolean selectableInsideRect(Selectable s, float px, float py,float pw,float ph) {
        float[][] points = getPoints(s);
        return  pointInsideRect(points[0][0],points[0][1],px,py,pw,ph) &&
                pointInsideRect(points[2][0],points[2][1],px,py,pw,ph) &&
                pointInsideRect(points[4][0],points[4][1],px,py,pw,ph) &&
                pointInsideRect(points[6][0],points[6][1],px,py,pw,ph);
    }

    public static boolean pointInsideRect(float px, float py, float rx, float ry, float rw, float rh){
        return (px > rx && py > ry && px < rx+rw && py < ry+rh);
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Edit logic">

    public void tick(ArrayList<? extends Selectable> objects){
        draw(objects);
        processEditCommands(objects);
    }

    private void draw(ArrayList<? extends Selectable> objects){
        //calculate points of selection items while drawing them
        synchronized (selection) {
            for (Selectable s : selection) {
                drawGizmo(s);
            }
        }

                
        //draw selection
        if(currentStatus == STATUS_SELECTING){
            drawSelection();
        }
    }

    private void processEditCommands(ArrayList objects) {
        KeyboardEvent k = null;
        while ((k = manager.getKeyEvent()) != null){
            if (k.keyCode == KeyEvent.VK_Q) { currentTransform = TRANSFORM_MOVE; }
//            else if (k.keyCode == KeyEvent.VK_W) { currentTransform = TRANSFORM_ROTATE; }
//            else if (k.keyCode == KeyEvent.VK_E) { currentTransform = TRANSFORM_SCALE; }
//            else if (k.keyCode == KeyEvent.VK_R) { currentTransform = TRANSFORM_SCALE_CENTERED; }
            else if (k.keyCode == KeyEvent.VK_SPACE &&
                    k.type == KeyboardEvent.TYPE_UP) { clearSelection(); }
            else if (k.keyCode == KeyEvent.VK_DELETE &&
                    k.type == KeyboardEvent.TYPE_UP) { deleteSelection(); }
            else if (k.keyCode == KeyEvent.VK_CONTROL) { isControlPressed = (k.type == KeyboardEvent.TYPE_DOWN); }
        }

        TouchEvent t = null;
        while ((t = manager.getTouchEvent()) != null) {
            if(t.n == 0){
                if (currentStatus == STATUS_IDLE) {
                    fromIdleStatus(t);
                } else if (currentStatus == STATUS_SELECTING) {
                    fromSelectingStatus(t,objects);
                } else if (currentStatus == STATUS_TRANSFORMING) {
                    fromTransformingStatus(t);
                }
            }
        }
    }

    private void fromIdleStatus(TouchEvent t) {
        if (t.type == TouchEvent.TYPE_DOWN) {
            //start transforming if click on ports or inside sprite
            int currentPort;
            selected = null;
            if(!isControlPressed){
                synchronized (selection) {

                    for (Selectable s : selection) {
                        currentPort = pointInsidePort(s, t.x, t.y);
                        if (currentPort != -1) {
                            portTransforming = (byte)currentPort;
                            selected = s;
                            break;
                        }
                        if (selected == null ) {
                            if (pointInsideSelectable(s, t.x, t.y)) {
                                
                                selected = s;


                            }
                        }
                    }
                }
            }
            selectionX = t.x;
            selectionY = t.y;
            if (selected != null) {
                if (portTransforming != -1) {
                    forceMoveTransform = false;
                } else {
                    forceMoveTransform = true;
                }
                calculateSelectionCenter();
                currentStatus = STATUS_TRANSFORMING;
                if (currentTransform == TRANSFORM_MOVE || forceMoveTransform) {
                    saveSelectionPreviousState(USE_POSITION );
                } else if (currentTransform == TRANSFORM_ROTATE) {
                    saveSelectionPreviousState(USE_POSITION | USE_ROT);
                } else if (currentTransform == TRANSFORM_SCALE) {
                    saveSelectionPreviousState(USE_POSITION | USE_SCALEX | USE_SCALEY);
                } else if (currentTransform == TRANSFORM_SCALE_CENTERED) {
                    saveSelectionPreviousState(USE_SCALEX | USE_SCALEY);
                }
                //else start selecting
            } else {
                if(!isControlPressed){
                    selection.clear();
                    selectionHash.clear();
                }
                useSimpleSelection = true;
                currentStatus = STATUS_SELECTING;
                selectionXEnd = selectionX;
                selectionYEnd = selectionY;
            }
        }
    }

    private void fromSelectingStatus(TouchEvent t, ArrayList objects) {
        if (t.type == TouchEvent.TYPE_DRAG) {
            selectionXEnd = t.x;
            selectionYEnd = t.y;
            useSimpleSelection = false;
        } else if (t.type == TouchEvent.TYPE_UP) {
                float aux;
                if (selectionXEnd < selectionX) {
                    aux = selectionXEnd;
                    selectionXEnd = selectionX;
                    selectionX = aux;
                }
                if (selectionYEnd < selectionY) {
                    aux = selectionYEnd;
                    selectionYEnd = selectionY;
                    selectionY = aux;
                }
                if (Math.max(selectionXEnd-selectionX, selectionYEnd-selectionY) < 25.0f){
                    useSimpleSelection = true;
                }

            if (useSimpleSelection) {
                //select first sprite containing point
                synchronized (objects) {
                    int size = objects.size();
                    for (int i = size - 1; i >= 0; i--) {
                        Object e = objects.get(i);
                        if (e instanceof Selectable) {
                            Selectable s = (Selectable) e;
                            if (pointInsideSelectable(s, selectionX, selectionY)) {
                                synchronized (selection) {
                                    if (!selectionHash.containsKey(s)) {
                                        selection.add(s);
                                        selectionHash.put(s, s);
                                    }else{
                                        selection.remove(s);
                                        selectionHash.remove(s);
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            } else {
                //select all sprites inside selection
     
                synchronized (objects) {
                    for (Object e : objects) {
                        if (e instanceof Selectable) {
                            Selectable s = (Selectable) e;
                            if (selectableInsideRect(s, selectionX, selectionY,
                                    selectionXEnd - selectionX, selectionYEnd - selectionY)) {

                                synchronized (selection) {
                                    if (!selectionHash.containsKey(s)) {
                                        selection.add(s);
                                        selectionHash.put(s, s);
                                    }
                                }

                            }
                        }
                    }

                }
            }
            manager.getFrame().getModel().setSelection(selection);
            currentStatus = STATUS_IDLE;
        }
    }

    private void fromTransformingStatus(TouchEvent t) {
        if (t.type == TouchEvent.TYPE_DRAG) {
            //moving transforming with drag
            selectionXEnd = t.x;
            selectionYEnd = t.y;
            if (currentTransform == TRANSFORM_MOVE || forceMoveTransform) {
                moveTransform();
            } else if (currentTransform == TRANSFORM_ROTATE) {
                rotateTransform();
            } else if (currentTransform == TRANSFORM_SCALE) {
                scaleTransform();
            } else if (currentTransform == TRANSFORM_SCALE_CENTERED) {
                scaleCenteredTransform();
            }
        } else if (t.type == TouchEvent.TYPE_UP) {
            applySelectionTransform();
            currentStatus = STATUS_IDLE;
            portTransforming = -1;
        }
    }




    private void moveTransform() {
        float deltaX = selectionXEnd - selectionX;
        float deltaY = selectionYEnd - selectionY;
        synchronized (selection) {
            int index = 0;
            for (Selectable s : selection) {
                s.setPosition(deltaX + oldX[index], deltaY + oldY[index]);
                index++;
            }
        }
    }

    private void rotateTransform() {
        float firstAngle = (float)Math.atan2(selectionX - selCenterX, selectionY - selCenterY);
        float lastAngle =  (float)Math.atan2(selectionXEnd - selCenterX, selectionYEnd - selCenterY);
        float diffAngle = lastAngle - firstAngle;
        synchronized (selection) {
            int index = 0;
            for (Selectable s : selection) {
                if(s instanceof Rotable){
                    Rotable r = (Rotable)s;
                    float x = oldX[index];
                    float y = oldY[index];
                    float tempX, tempY;
                    r.setRotation((float)(diffAngle*180.0/Math.PI) + oldRotation[index]);
                    tempX = x - selCenterX;
                    tempY = y - selCenterY;
                    float cos = (float) Math.cos(diffAngle);
                    float sin = (float) Math.sin(diffAngle);
                    s.setPosition( (tempX * cos + tempY * sin) + selCenterX,
                                   (-tempX * sin + tempY * cos) + selCenterY );
                }
                index++;
            }
        }
    }

    private void scaleCenteredTransform() {
        float rot = 0.0f;
        if (selected instanceof Rotable){
            rot = ((Rotable)selected).getRotation();
        }
        float selX = selected.getX();
        float selY = selected.getY();
        float[] start = rotatePoint(rot, selectionX, selectionY, selX, selY);
        float[] end = rotatePoint(rot, selectionXEnd, selectionYEnd,selX, selY);
        float deltaX = (end[0] - start[0]) * 2;
        float deltaY = (end[1] - start[1]) * 2;
        if (portTransforming == 0) {
            deltaX = -deltaX;
        } else if (portTransforming == 1) {
            deltaX = 0;
        } else if (portTransforming == 2) {
        } else if (portTransforming == 3) {
            deltaY = 0;
        } else if (portTransforming == 4) {
            deltaY = -deltaY;
        } else if (portTransforming == 5) {
            deltaX = 0;
            deltaY = -deltaY;
        } else if (portTransforming == 6) {
            deltaX = -deltaX;
            deltaY = -deltaY;
        } else if (portTransforming == 7) {
            deltaX = -deltaX;
            deltaY = 0;
        }

        synchronized (selection) {
            int index = 0;
            for (Selectable s : selection) {
                if(s instanceof Scalable){
                    Scalable r = (Scalable)s;
                    float x = oldScaleX[index];
                    float y = oldScaleY[index];
                    r.setScaleX( x + deltaX/r.getSizeX());
                    r.setScaleY( y + deltaY/r.getSizeY());
                }
                index++;
            }
        }
    }

    private void scaleTransform() {
        float rot = 0.0f;
        if (selected instanceof Rotable){
            rot = ((Rotable)selected).getRotation();
        }
        float selX = selected.getX();
        float selY = selected.getY();
        float[] start = rotatePoint(rot, selectionX, selectionY, selX, selY);
        float[] end = rotatePoint(rot, selectionXEnd, selectionYEnd,selX, selY);
        float deltaX = end[0] - start[0];
        float deltaY = end[1] - start[1];
        float oDeltaX = (selectionXEnd-selectionX)/2.0f;
        float oDeltaY = (selectionYEnd-selectionY)/2.0f;
        if (portTransforming == 0) {
            deltaX = -deltaX;
        } else if (portTransforming == 1) {
            deltaX = 0;
        } else if (portTransforming == 2) {

        } else if (portTransforming == 3) {
            deltaY = 0;
        } else if (portTransforming == 4) {
            deltaY = -deltaY;
        } else if (portTransforming == 5) {
            deltaX = 0;
            deltaY = -deltaY;
        } else if (portTransforming == 6) {
            deltaX = -deltaX;
            deltaY = -deltaY;
        } else if (portTransforming == 7) {
            deltaX = -deltaX;
            deltaY = 0;
        }

        synchronized (selection) {
            int index = 0;
            for (Selectable s : selection) {
                if(s instanceof Scalable){
                    Scalable r = (Scalable)s;
                    float sx = oldScaleX[index];
                    float sy = oldScaleY[index];
                    float x = oldX[index];
                    float y = oldY[index];
                    r.setScaleX( sx + deltaX/r.getSizeX());
                    r.setScaleY( sy + deltaY/r.getSizeY());
                    r.setPosition(x + oDeltaX,y + oDeltaY);
                }
                index++;
            }
        }
    }

 
     private float[] rotatePoint(float grads, float x, float y, float px, float py){
        float tx,ty;
        tx = x - px; ty = y - py;
        float cos = (float) Math.cos(grads * Math.PI / 180.0f);
        float sin = (float) Math.sin(grads * Math.PI / 180.0f);
        x = tx * cos - ty * sin;
        y = -tx * sin - ty * cos;
        return new float[]{x+px,y+py};
    }



    public void setTransform(byte t) {
        currentTransform = t;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="selection">

    public void deleteSelection(){
        synchronized(selection){
            manager.getModel().deleteEntries(selection);
        }
        clearSelection();
    }

    public void setSelection(ArrayList<Selectable> objects){
        synchronized(selection){
            if(currentStatus != STATUS_TRANSFORMING){
                selection.clear();
                selectionHash.clear();
                clearOldValues();
                for(Selectable s : objects){
                        selection.add(s);
                        selectionHash.put(s, s);
                }
                calculateSelectionCenter();
            }
        }
    }

    public boolean isInSelection(Selectable s){
        synchronized(selection){
            return selectionHash.containsKey(s);
        }
    }

    

    private void calculateSelectionCenter() {
        synchronized (selection) {
            float selX = 0.0f, selY = 0.0f; int size = 0;
            for (Selectable s : selection) {
                if(s instanceof Moveable){
                    Moveable m = (Moveable)s;
                    selX += m.getX();
                    selY += m.getY();
                    size++;
                }
            }
            if (size != 0) {
                selX /= size;
                selY /= size;
            }
            selCenterX = selX;
            selCenterY = selY;
        }
    }

    private void clearSelection() {
        synchronized(selection){
            selection.clear();
            selectionHash.clear();
            manager.getFrame().getModel().setSelection(selection);
        }
    }

    // </editor-fold>

}
